/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at http://curl.haxx.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ***************************************************************************/
/* This is an example application source code using the multi interface. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* somewhat unix-specific */
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>

/* curl stuff */
#include <curl/curl.h>

/*
 * Download many HTTP files simultaneously.
 */

#define MAX_CULR_NUM  (64)

 typedef struct download_ctx_s{
   /* data */
    int fd;
    off_t wrote_bytes;
    CURL *curl;
 }download_ctx_t;

 typedef struct dservice_ctx_s{
  CURLM *handle;
  download_ctx_t slots[MAX_CULR_NUM];
 }dservice_ctx_t;


 static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata){
  download_ctx_t *dc = (download_ctx_t*)userdata;
  if(dc && dc->fd == -1){
    // open fd for write
    dc->fd = 0;
    fprintf(stderr, "%s\n", "fake open file fd to write");
  }
  dc->wrote_bytes += size * nmemb;
  return size*nmemb;
 }

static int dservice_startup(dservice_ctx_t* ds){
  int i = 0;
  download_ctx_t *dc;
  ds->handle = curl_multi_init();
  for(; i < MAX_CULR_NUM; i++){
    dc = &ds->slots[i];
    dc->fd = -1;
    dc->wrote_bytes = 0;
    dc->curl = NULL;
  }

  return 0;
}

static int dservice_cleanup(dservice_ctx_t *ds){
  int i = 0;
  download_ctx_t *dc;
  curl_multi_cleanup(ds->handle);
  ds->handle = NULL;
  for(; i < MAX_CULR_NUM; i++){
    dc = &ds->slots[i];
    if(dc->curl){
      curl_easy_cleanup(dc->curl);
      dc->curl = NULL;
      dc->fd = -1;
      dc->wrote_bytes = 0;
    }
  }
  return 0;
}

static int dservice_perform(dservice_ctx_t* ds){
  int still_running = 0;
  CURLM *multi_handle = ds->handle;
  curl_multi_perform(multi_handle, &still_running);
  
  // handle message for curl
  if(1){
    CURLMsg *msg; /* for picking up messages with the transfer status */
    int msgs_left; /* how many messages are left */
    while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
      if (msg->msg == CURLMSG_DONE) {
        int idx, found_idx = -1;

        /* Find out which handle this message is about */
        for (idx=0; idx < MAX_CULR_NUM; idx++) {
          if(msg->easy_handle == ds->slots[idx].curl){
            found_idx = idx;
            break;
          }
        }
        if(found_idx != -1){
          download_ctx_t *dc = &ds->slots[found_idx];
          fprintf(stderr, "mission: %p completed with status %d\n", dc->curl, msg->data.result);
          curl_multi_remove_handle(multi_handle, dc->curl);
          curl_easy_cleanup(dc->curl);
          dc->fd = -1;
          dc->curl = NULL;
          dc->wrote_bytes = 0;
          break;
        }
      }
    }
  }

  if(1){
    /*use wait*/
    struct timeval timeout;
    int rc; /* select() return code */

    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd = -1;

    long curl_timeo = -1;

    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);

    /* set a suitable timeout to play around with */
    timeout.tv_sec = 0;
    timeout.tv_usec = 5000;

    curl_multi_timeout(multi_handle, &curl_timeo);
    if(curl_timeo >= 0) {
        timeout.tv_usec = (curl_timeo % 1000) * 1000;
        if(timeout.tv_usec > 5000)
          timeout.tv_usec = 5000;
    }

    /* get file descriptors from the transfers */
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

    /* In a real-world program you OF COURSE check the return code of the
       function calls.  On success, the value of maxfd is guaranteed to be
       greater or equal than -1.  We call select(maxfd + 1, ...), specially in
       case of (maxfd == -1), we call select(0, ...), which is basically equal
       to sleep. */
    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
  }

  return still_running;
}

#define MAX_RESOURCE_NUM  (3)

static int dservice_add(dservice_ctx_t *ds){
  const char *s_resource[MAX_RESOURCE_NUM] = {
    "http://mirrors.sohu.com/ubuntu-releases/12.04.2/release/ubuntu-12.04.2-alternate-amd64%2bmac.iso",
    "http://mirrors.sohu.com/ubuntu-releases/12.04.2/release/ubuntu-12.04.2-desktop-amd64%2bmac.iso",
    "http://mirrors.sohu.com/ubuntu-releases/12.04.2/release/ubuntu-12.04.2-alternate-amd64%2bmac.template",
  };
  int idx_url = rand()%MAX_RESOURCE_NUM;
  int idx = 0;
  download_ctx_t *dc;
  for(; idx < MAX_CULR_NUM; idx++){
    dc = &ds->slots[idx];
    if(!dc->curl){
      dc->curl = curl_easy_init();
      curl_easy_setopt(dc->curl, CURLOPT_URL, s_resource[idx_url]);
      curl_easy_setopt(dc->curl, CURLOPT_WRITEFUNCTION, write_cb);
      curl_easy_setopt(dc->curl, CURLOPT_WRITEDATA, dc);
      curl_multi_add_handle(ds->handle, dc->curl);
      fprintf(stderr, "add '%p' mission: '%s'\n", dc->curl, s_resource[idx_url]);
      break;
    }
  }

  return 0;
}

static int dservice_status(dservice_ctx_t* ds){
  int idx = 0;
  download_ctx_t *dc;
  fprintf(stderr, "%s\n", "/*-------------------------------------------------");
  for(; idx < MAX_CULR_NUM; idx++){
    dc = &ds->slots[idx];
    if(dc->curl){
      fprintf(stderr, "[%d], curl: %p, wrote_bytes: %llu\n", idx, dc->curl, dc->wrote_bytes);
    }
  }
  fprintf(stderr, "%s\n", "-------------------------------------------------*/");

  return 0;
}

int main(void)
{
  dservice_ctx_t ds;
  char buffer[1024];
  int readed = 0, stop_loop = 0;
  if(1){
    int flags;
    flags = fcntl(STDIN_FILENO, F_GETFL);
    flags |= O_NONBLOCK;
    if(fcntl(STDIN_FILENO, F_SETFL, flags) == -1){
      fprintf(stderr, "%s\n", "fcntl set STDIN_FILENO O_NONBLOCK error");
      return -1;
    }
  }
  /* init a multi stack */
  dservice_startup(&ds);

  while(!stop_loop){
    readed = read(STDIN_FILENO, buffer, 1024);
    if(readed > 0){
      char cch = buffer[0];
      switch(cch){
        default:
        case 'h':
          fprintf(stderr, "%s\n", "usage: h|a|l|e");
          break;
        case 'a':
          dservice_add(&ds);
          break;
        case 'e':
          stop_loop = 1;
          break;
        case 'l':
          dservice_status(&ds);
          break;
      }
    }
    dservice_perform(&ds);
  }

  dservice_cleanup(&ds);

  return 0;
}